/*
 * Run a programme with a given environment, any environment
 *
 * Copyright 2022 and 2023 Odin Kroeger.
 *
 * This file is part of suCGI.
 *
 * suCGI is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * suCGI is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with suCGI. If not, see <https://www.gnu.org/licenses/>.
 */

#define _BSD_SOURCE
#define _DARWIN_C_SOURCE
#define _DEFAULT_SOURCE
#define _GNU_SOURCE

#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>


/*
 * Macros
 */

/* The number 10 */
#define BASE10 10

/* Status to exit with on usage errors */
#define EXIT_USAGE 2


/*
 * Global variables
 */

/* Environment */
extern char **environ;


/*
 * Main
 */

int
main (int argc, char **argv)
{
    long nenv = 0;
    long nvars = 0;
    bool inherit = true;

    int opt;
    /* RATS: ignore; badenv is not security-critical */
    while ((opt = getopt(argc, argv, "in:h")) != -1) {
        switch (opt) {
        case 'h':
            (void) puts(
"badenv - run a programme with a given environment, any environment\n\n"
"Usage:    badenv [-i] [-nN] [var ...] [cmd] [arg ...]\n"
"          badenv -h\n\n"
"Operands:\n"
"    var   Environment variable. Use -n if it does not contain a '='.\n"
"    cmd   Command to run. Not searched for in $PATH.\n"
"    arg   Argument for cmd.\n\n"
"Options:\n"
"    -i    Do not inherit the current environment.\n"
"    -n N  Treat the first N arguments as environment variables.\n"
"    -h    Print this help screen.\n\n"
"Copyright 2022 and 2023 Odin Kroeger.\n"
"Released under the GNU Affero General Public License.\n"
"This programme comes with ABSOLUTELY NO WARRANTY."
            );
            return EXIT_SUCCESS;
        case 'i':
            inherit = false;
            break;
        case 'n':
            errno = 0;
            nvars = strtol(optarg, NULL, BASE10);
            if (nvars == 0 && errno != 0) {
                err(EXIT_USAGE, "-n");
            }
            if (nvars < 0) {
                errx(EXIT_USAGE, "-n: %ld is negative", nvars);
            }
            if (nvars > SHRT_MAX) {
                errx(EXIT_USAGE, "-n: %ld is too large", nvars);
            }
            break;
        default:
            return EXIT_USAGE;
        }
    }

    argc -= optind; 
    argv += optind;     /* cppcheck-suppress misra-c2012-18.4; idiomatic */

    if (nvars > (long) argc) {
        errx(EXIT_USAGE, "-n: Only %d arguments given", argc);
    }

    if (inherit) {
        while (environ[nenv] != NULL) {
            ++nenv;
        }
    }

    if (nenv > SHRT_MAX) {
        errx(EXIT_FAILURE, "Environment is too full");
    }

    if (nvars == 0) {
        while (nvars < argc && argv[nvars] && strchr(argv[nvars], '=')) {
            ++nvars;
        }
    }

    errno = 0;
    /* cppcheck-suppress misra-c2012-10.8; no information loss */
    char **vars = calloc((size_t) (nenv + nvars + 1), sizeof(*vars));
    if (!vars) {
        err(EXIT_FAILURE, "calloc");
    }

    /*
     * New variables are simply inserted before the current ones,
     * so there may be duplicates; this is BADenv, after all.
     */

    if (nvars > 0) {
        if ((size_t) nvars > SIZE_MAX / sizeof(*argv)) {
            errx(EXIT_FAILURE, "Too many variables");
        }

        /* RATS: ignore; vars is large enough */
        (void) memcpy(vars, argv, (size_t) nvars * sizeof(*argv));
    }

    if (nenv > 0) {
        if ((size_t) nenv > SIZE_MAX / sizeof(*environ)) {
            errx(EXIT_FAILURE, "Environment got too large");
        }

        /* RATS: ignore; vars is large enough */
        (void) memcpy(&vars[nvars], environ, (size_t) nenv * sizeof(*environ));
    }

    char **cmd = &argv[nvars];
    if (*cmd) {
        errno = 0;
        /* RATS: ignore; safe enough */
        (void) execve(*cmd, cmd, vars);
        err(EXIT_FAILURE, "exec %s", *cmd);
    }

    for (; *vars; vars++) {
        (void) puts(*vars);
    }

    /* NOLINTNEXTLINE(clang-analyzer-unix.Malloc) */
    return EXIT_SUCCESS;
}

